#ifndef TIMER_H
#define TIMER_H

#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <iostream>

//#include <windows.h>
//#include <winnt.h>
#include <mach/mach.h>
#include <mach/mach_time.h>

#include <OpenGLES/ES2/gl.h>

class Timer
{
public:
	Timer() {
        mach_timebase_info(&timebase_info);
    }
	~Timer() {}

	void reset() { _sections.clear(); };
	void start()
	{
		if (_enabled)
			if (_flush_enabled)
				flush();

		_accum = 0.0f;
		//QueryPerformanceCounter((LARGE_INTEGER*)&_t0);
        static uint64_t t = 0;
        _t0 = mach_absolute_time();
        _total = abs_to_nanos(_t0 - t) / 1000000000.0f;
        t = _t0;
	}

    static uint64_t abs_to_nanos(uint64_t abs)
    {
        return abs * timebase_info.numer / timebase_info.denom;
    }
    
    static float now()
    {
        return abs_to_nanos(mach_absolute_time()) / 1000000000.0f;
    }
    
	float clock(const std::string & msg = "")
	{
		if (_enabled)
		{
			if (_flush_enabled)
				flush();
		
			uint64_t t1;
			//QueryPerformanceCounter((LARGE_INTEGER*)&t1);
            t1 = mach_absolute_time();
			//QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
			//float t = float(double(t1 - _t0) / double(freq));
            float t = abs_to_nanos(t1 - _t0) / 1000000000.0f;
			//printf("%ld %ld %f\n", t1, freq, t);

			float m = mean(msg, t);
			_accum += m;

			//QueryPerformanceCounter((LARGE_INTEGER*)&_t0);
            _t0 = mach_absolute_time();

			return m;
		}
		else
		{
			return 0.0f;
		}
	}

	void set_enabled(bool enabled = true)
	{
		_enabled = enabled;
	}

	friend std::ostream &operator<<(std::ostream &out, const Timer &timer)
	{
		for (std::map<std::string, Timer::Section>::const_iterator section = timer._sections.begin();
			section != timer._sections.end();
			section++) {
			const std::string &name = section->first;
			float mean = section->second.mean / timer._repetition_count;
			float accum = timer._accum / timer._repetition_count;
			out << name << " : " << 1000.0f * mean << "ms | " << int(100.0f * mean / accum) << "% | " << int(1.0 / mean) << "fps [" << int(100.0f * section->second.completed) << "%]\n";
		}
        
        out << "Total " <<  1000.0f * timer._total << "ms | " << 1.0f / timer._total << "fps\n";
		return out;
	}

private:
	float mean(const std::string& msg, float t)
	{
		Section &section = _sections[msg];
		if (_window_size > 1) {
			section.buffer.resize(_window_size, std::make_pair(0.0f, false));
			section.buffer[(section.pos++) % _window_size] = std::make_pair(t, true);

			section.mean = 0.0;
			float n = 0;
			for (int i = 0; i < int(section.buffer.size()); i++) {
				std::pair<float, bool> val = section.buffer[i];
				if (val.second) {
					section.mean += val.first;
					n++;
				}
			}
			section.mean /= n;

			if (section.completed < 1.0f)
				section.completed = float(section.pos - 1) / _window_size;

			return section.mean;
		}
		else {
			section.mean = t;
			return section.mean;
		}
	}
	void flush()
	{
		// TODO
		glFinish();
	}

	uint64_t _t0		= 0;
	float _accum	= 0.0f;
    float _total    = 0.0f;
	
	bool _enabled			= true;
	bool _flush_enabled		= false;
	int _window_size		= 10;
	int _repetition_count	= 1;

	class Section
	{
	public:
		Section() {}

		std::vector<std::pair<float, bool> > buffer;
		float mean		= 0.0f;
		int pos			= 0;
		float completed = 0.0f;
	};

	std::map<std::string, Section> _sections;
    
    static mach_timebase_info_data_t timebase_info;
};

#endif